package com.trackerapp.network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URLEncoder;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;

import javax.net.ssl.X509TrustManager;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.scheme.LayeredSocketFactory;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;

import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.trackerapp.R;
import com.trackerapp.util.NetworkConnectionUtil;
import com.trackerapp.util.ResourcesUtil;

public class HttpConnection implements Runnable {
    private static final String TAG = HttpConnection.class.getSimpleName();

    private static final int TIME_OUT_CONNECTION = 30000;
    private static final int TIME_OUT_SOCKET = 30000;

    public static final int REQUEST_START = 0;
    public static final int REQUEST_ERROR = 1;
    public static final int REQUEST_SUCCEED = 2;
    public static final int INVALID_SESSION = 3;

    private static final int GET = 0;
    private static final int POST = 1;
    private static final int PUT = 2;
    private static final int DELETE = 3;
    private static final int SOAP = 4;
    private static final int BITMAP = 5;

    private ArrayList<NameValuePair> params;
    private ArrayList<NameValuePair> headers;
    private NameValuePair cookie;
    private String url;
    private int method;
    private Handler handler;
    private String data;

    private HttpClient httpClient;

    public HttpConnection() {
//        this(new Handler());
        params = new ArrayList<NameValuePair>();
        headers = new ArrayList<NameValuePair>();
    }

    public HttpConnection(Handler _handler) {
        handler = _handler;
        params = new ArrayList<NameValuePair>();
        headers = new ArrayList<NameValuePair>();
    }

    public void addParam(String name, String value) {
        params.add(new BasicNameValuePair(name, value));
    }

    public void addHeader(String name, String value) {
        headers.add(new BasicNameValuePair(name, value));
    }

    public void addCookie(String name, String value) {
        cookie = new BasicNameValuePair(name, value);
    }

    public void create(int method, String url, String data) {
        this.method = method;
        this.url = url;
        this.data = data;
        HttpConnectionManager.getInstance().push(this);
        System.out.println(" ##### create ##### ");
    }

    public void get(String url) {
        create(GET, url, null);
    }

    public void post(String url, String data) {
        create(POST, url, data);
    }

    public void post(String url) {
        create(POST, url, null);
    }

    public void put(String url, String data) {
        create(PUT, url, data);
    }
    
    public void put(String url) {
        create(PUT, url, null);
    }

    public void bitmap(String url) {
        create(BITMAP, url, null);
    }

    public void delete(String url) {
        create(DELETE, url, null);
    }

    public void run() {
    	System.out.println(" ##### run ##### ");
        if (!NetworkConnectionUtil.isOnline()) {
            HttpConnectionManager.getInstance().didComplete(this);
            handler.sendMessage(Message.obtain(handler, HttpConnection.REQUEST_ERROR, ResourcesUtil.getString(R.string.network_unavailable)));
            return;
        }
        if (url == null || "".equals(url)) {
            HttpConnectionManager.getInstance().didComplete(this);
            handler.sendMessage(Message.obtain(handler, HttpConnection.REQUEST_ERROR, ResourcesUtil.getString(R.string.invalid_url)));
            return;
        }
        try {
            handler.sendMessage(Message.obtain(handler, HttpConnection.REQUEST_START));
            /* Basic http params */
            HttpParams httpParams = new BasicHttpParams();
            ConnManagerParams.setTimeout(httpParams, TIME_OUT_SOCKET);

            HttpConnectionParams.setConnectionTimeout(httpParams, TIME_OUT_CONNECTION);
            HttpConnectionParams.setSoTimeout(httpParams, TIME_OUT_SOCKET);
            HttpProtocolParams.setContentCharset(httpParams, "utf-8");
            HttpConnectionParams.setTcpNoDelay(httpParams, true);
            HttpConnectionParams.setSocketBufferSize(httpParams, 8192);
            HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);

            /* Register schemes, HTTP and HTTPS */
            SchemeRegistry registry = new SchemeRegistry();
            registry.register(new Scheme("http", new PlainSocketFactory(), 80));

            /* Make a thread safe connection manager for the client */
            ThreadSafeClientConnManager manager = new ThreadSafeClientConnManager(httpParams, registry);
            httpClient = new DefaultHttpClient(manager, httpParams);

            HttpResponse response = null;
            switch (method) {
                case GET:
                    String query = formQueryString();
                    HttpGet httpGet = new HttpGet(url + query);
                    if (cookie != null) {
//                        Cookie cookie = new BasicClientCookie(c.getName(), c.getValue());
//                        cookieStore.addCookie(cookie);
                        httpGet.addHeader("Cookie", cookie.getName() + "=" + cookie.getValue());
                    }
                    for (NameValuePair h : headers) {
                        httpGet.addHeader(h.getName(), h.getValue());
                    }
//                    localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
                    response = httpClient.execute(httpGet);
                    break;
                case POST:
                	System.out.println(" ##### POST ##### ");
                    HttpPost httpPost = new HttpPost(url);
                    if (cookie != null) {
//                        Cookie cookie = new BasicClientCookie(c.getName(), c.getValue());
//                        cookieStore.addCookie(cookie);
                        httpPost.addHeader("Cookie", cookie.getName() + "=" + cookie.getValue());
                    }
                    for (NameValuePair h : headers) {
                        httpPost.addHeader(h.getName(), h.getValue());
                        System.out.println(" ##### POST ##### "+h.getName()+"  : "+h.getValue());
                    }
                    if (!params.isEmpty()) {
                        httpPost.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
                    }
                    if (data != null) {
                        httpPost.setEntity(new StringEntity(data, HTTP.UTF_8));
                    }
                    System.out.println(" ###### POST 11 ####### ");
                    response = httpClient.execute(httpPost);
                    break;
                case PUT:
                    HttpPut httpPut = new HttpPut(url);
                    for (NameValuePair h : headers) {
                        httpPut.addHeader(h.getName(), h.getValue());
                    }
                    if (cookie != null) {
                        httpPut.addHeader("Cookie", cookie.getName() + "=" + cookie.getValue());
                    }
                    if (!params.isEmpty()) {
                        httpPut.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
                    }
                    if (data != null) {
                        httpPut.setEntity(new StringEntity(data));
                    }
                    response = httpClient.execute(httpPut);
                    break;
                case DELETE:
                    HttpDelete httpDelete = new HttpDelete(url);
                    if (cookie != null) {
                        httpDelete.addHeader("Cookie", cookie.getName() + "=" + cookie.getValue());
                    }
                    for (NameValuePair h : headers) {
                        httpDelete.addHeader(h.getName(), h.getValue());
                    }
                    response = httpClient.execute(httpDelete);
                    break;
                case SOAP:
                    HttpPost httpSoap = new HttpPost(url);
                    httpSoap.setEntity(new StringEntity(data, HTTP.UTF_8));
                    response = httpClient.execute(httpSoap);
                    break;
            }
            if (method != BITMAP) {
                processEntity(response);
            }
        } catch (Exception e) {
        	e.printStackTrace();
            handler.sendMessage(Message.obtain(handler, HttpConnection.REQUEST_ERROR, ResourcesUtil.getString(R.string.error_service)));
        } finally {
            try {
                httpClient.getConnectionManager().closeExpiredConnections();
                httpClient.getConnectionManager().shutdown();
                HttpConnectionManager.getInstance().didComplete(this);
            } catch (Exception ex) {
                httpClient = null;
            }

        }

    }

    public void runImmediate() {
        try {
            if (!NetworkConnectionUtil.isOnline()) {
                Log.v(TAG, ResourcesUtil.getString(R.string.network_unavailable));
                return;
            }
            if (url == null || "".equals(url)) {
                Log.v(TAG, ResourcesUtil.getString(R.string.invalid_url));
                return;
            }
            /* Basic http params */
            HttpParams httpParams = new BasicHttpParams();
            ConnManagerParams.setTimeout(httpParams, TIME_OUT_SOCKET);

            HttpConnectionParams.setConnectionTimeout(httpParams, TIME_OUT_CONNECTION);
            HttpConnectionParams.setSoTimeout(httpParams, TIME_OUT_SOCKET);
            HttpProtocolParams.setContentCharset(httpParams, "utf-8");
            HttpConnectionParams.setTcpNoDelay(httpParams, true);
            HttpConnectionParams.setSocketBufferSize(httpParams, 8192);
            HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);

            /* Register schemes, HTTP and HTTPS */
            SchemeRegistry registry = new SchemeRegistry();
            registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));

            ThreadSafeClientConnManager manager = new ThreadSafeClientConnManager(httpParams, registry);
            httpClient = new DefaultHttpClient(manager, httpParams);
            // Create a local instance of cookie store
//            CookieStore cookieStore = new BasicCookieStore();
            // Create local HTTP context
//            HttpContext localContext = new BasicHttpContext();

            HttpResponse response = null;
            switch (method) {
                case SOAP:
                    HttpPost httpSoap = new HttpPost(url);

                    httpSoap.setEntity(new StringEntity(data, HTTP.UTF_8));
                    response = httpClient.execute(httpSoap);
                    break;
                case POST:
                    HttpPost httpPost = new HttpPost(url);
                    for (NameValuePair h : headers) {
                        httpPost.addHeader(h.getName(), h.getValue());
                    }
                    if (cookie != null) {
//                        Cookie cookie = new BasicClientCookie(c.getName(), c.getValue());
//                        cookieStore.addCookie(cookie);
                        httpPost.addHeader("Cookie", cookie.getName() + "=" + cookie.getValue());
                    }
                    if (!params.isEmpty()) {
                        httpPost.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
                    }
                    if (data != null) {
                        httpPost.setEntity(new StringEntity(data, HTTP.UTF_8));
                    }

                    response = httpClient.execute(httpPost);
                    break;
                case GET:
                    String query = formQueryString();
                    query = formQueryString();
                    HttpGet httpGet = new HttpGet(url + query);

                    if (cookie != null) {
//                        Cookie cookie = new BasicClientCookie(c.getName(), c.getValue());
//                        cookieStore.addCookie(cookie);
                        httpGet.addHeader("Cookie", cookie.getName() + "=" + cookie.getValue());
                    }
                    for (NameValuePair h : headers) {
                        httpGet.addHeader(h.getName(), h.getValue());
                    }
                    response = httpClient.execute(httpGet);
                    break;
                case PUT:
                    query = formQueryString();
                    HttpPut httpPut = new HttpPut(url + query);

                    for (NameValuePair h : headers) {
                        httpPut.addHeader(h.getName(), h.getValue());
                    }
                    if (cookie != null) {
                        httpPut.addHeader("Cookie", cookie.getName() + "=" + cookie.getValue());
                    }
                    if (!params.isEmpty()) {
                        httpPut.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
                    }
                    if (data != null) {
                        httpPut.setEntity(new StringEntity(data));
                    }

                    response = httpClient.execute(httpPut);
                    break;
                case DELETE:
                    query = formQueryString();
                    HttpDelete httpDelete = new HttpDelete(url + query);
                    for (NameValuePair h : headers) {
                        httpDelete.addHeader(h.getName(), h.getValue());
                    }
                    if (cookie != null) {
                        httpDelete.addHeader("Cookie", cookie.getName() + "=" + cookie.getValue());
                    }

                    response = httpClient.execute(httpDelete);
                    break;
            }
            
            processEntity(response);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

    private String formQueryString() {
        try {
            String combinedParams = "";
            if (!params.isEmpty()) {
                combinedParams += "?";
                for (NameValuePair p : params) {
                    String paramString = p.getName() + "=" + URLEncoder.encode(p.getValue(), "UTF-8");
                    if (combinedParams.length() > 1) {
                        combinedParams += "&" + paramString;
                    } else {
                        combinedParams += paramString;
                    }
                }
            }
            return combinedParams;
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    private void processEntity(HttpResponse response) throws Exception {
    	System.out.println(" ##### processEntity ##### ");
        int statusCode = response.getStatusLine().getStatusCode();
        String statusMessage = response.getStatusLine().getReasonPhrase();
        Log.v(TAG, "statusCode::" + statusCode + "::statusMessage::" + statusMessage);
        HttpEntity entity = response.getEntity();

        BufferedReader br = new BufferedReader(new InputStreamReader(entity.getContent()));
        char[] chars = new char[8 * 1024];
        int len;
        StringBuffer buf = new StringBuffer();
        while ((len = br.read(chars)) >= 0) {
            buf.append(chars, 0, len);
        }
        String result = buf.toString().trim();
        System.out.println(" ##### processEntity ##### "+result);
        if (handler != null) {
            Message message = Message.obtain(handler, REQUEST_SUCCEED, result);
            handler.sendMessage(message);
        }
    }

    public class TrivialTrustManager implements X509TrustManager {
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws
                CertificateException {
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType) throws
                CertificateException {
        }

        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }

    private void workAroundReverseDnsBugInHoneycombAndEarlier(HttpClient client) {
        // Android had a bug where HTTPS made reverse DNS lookups (fixed in Ice Cream Sandwich)
        // http://code.google.com/p/android/issues/detail?id=13117

        SocketFactory socketFactory = new LayeredSocketFactory() {
            SSLSocketFactory delegate = SSLSocketFactory.getSocketFactory();

            @Override
            public Socket createSocket() throws IOException {
                return delegate.createSocket();
            }

            @Override
            public Socket connectSocket(Socket sock, String host, int port,
                                        InetAddress localAddress, int localPort, HttpParams params) throws IOException {
                return delegate.connectSocket(sock, host, port, localAddress, localPort, params);
            }

            @Override
            public boolean isSecure(Socket sock) throws IllegalArgumentException {
                return delegate.isSecure(sock);
            }

            @Override
            public Socket createSocket(Socket socket, String host, int port,
                                       boolean autoClose) throws IOException {
                injectHostname(socket, host);
                return delegate.createSocket(socket, host, port, autoClose);
            }

            private void injectHostname(Socket socket, String host) {
                try {
                    Field field = InetAddress.class.getDeclaredField("hostName");
                    field.setAccessible(true);
                    field.set(socket.getInetAddress(), host);
                } catch (Exception ignored) {
                }
            }
        };
        client.getConnectionManager().getSchemeRegistry().register(new Scheme("http", new PlainSocketFactory(), 80));
    }

}
